/**
 * @author qiao / https://github.com/qiao
 * @author mrdoob / http://mrdoob.com
 * @author alteredq / http://alteredqualia.com/
 * @author WestLangley / http://github.com/WestLangley
 * @author erich666 / http://erichaines.com
 */
/*global THREE, console */

// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
// supported.
//
//    Orbit - left mouse / touch: one finger move
//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
//    Pan - right mouse, or arrow keys / touch: three finter swipe
//
// This is a drop-in replacement for (most) TrackballControls used in examples.
// That is, include this js file and wherever you see:
//    	controls = new THREE.TrackballControls( camera );
//      controls.target.z = 150;
// Simple substitute "OrbitControls" and the control should work as-is.
// module.exports =
export default  function (THREE) {

	function OrbitControls(object, domElement) {
		this.object = object;
		this.domElement = (domElement !== undefined) ? domElement : document;

		// API

		// Set to false to disable this control
		this.enabled = true;

		// "target" sets the location of focus, where the control orbits around
		// and where it pans with respect to.
		this.target = new THREE.Vector3();

		// center is old, deprecated; use "target" instead
		this.center = this.target;

		// This option actually enables dollying in and out; left as "zoom" for
		// backwards compatibility
		this.noZoom = false;
		this.zoomSpeed = 1.0;

		// Limits to how far you can dolly in and out
		this.minDistance = 0;
		this.maxDistance = Infinity;

		// Set to true to disable this control
		this.noRotate = false;
		this.rotateSpeed = 1.0;

		// Set to true to disable this control
		this.noPan = false;
		this.keyPanSpeed = 7.0;	// pixels moved per arrow key push

		// Set to true to automatically rotate around the target
		this.autoRotate = false;
		this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60

		// How far you can orbit vertically, upper and lower limits.
		// Range is 0 to Math.PI radians.
		this.minPolarAngle = 0; // radians
		this.maxPolarAngle = Math.PI; // radians

		// Set to true to disable use of the keys
		this.noKeys = false;

		// The four arrow keys
		this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };

		////////////
		// internals

		var scope = this;

		var EPS = 0.000001;

		var rotateStart = new THREE.Vector2();
		var rotateEnd = new THREE.Vector2();
		var rotateDelta = new THREE.Vector2();

		var panStart = new THREE.Vector2();
		var panEnd = new THREE.Vector2();
		var panDelta = new THREE.Vector2();
		var panOffset = new THREE.Vector3();

		var offset = new THREE.Vector3();

		var dollyStart = new THREE.Vector2();
		var dollyEnd = new THREE.Vector2();
		var dollyDelta = new THREE.Vector2();

		var phiDelta = 0;
		var thetaDelta = 0;
		var scale = 1;
		var pan = new THREE.Vector3();

		var lastPosition = new THREE.Vector3();

		var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };

		var state = STATE.NONE;

		// for reset

		this.target0 = this.target.clone();
		this.position0 = this.object.position.clone();

		// events

		var changeEvent = { type: 'change' };
		var startEvent = { type: 'start' };
		var endEvent = { type: 'end' };

		this.rotateLeft = function (angle) {

			if (angle === undefined) {

				angle = getAutoRotationAngle();

			}

			thetaDelta -= angle;

		};

		this.rotateUp = function (angle) {

			if (angle === undefined) {

				angle = getAutoRotationAngle();

			}

			phiDelta -= angle;

		};

		// pass in distance in world space to move left
		this.panLeft = function (distance) {

			var te = this.object.matrix.elements;

			// get X column of matrix
			panOffset.set(te[0], te[1], te[2]);
			panOffset.multiplyScalar(- distance);

			pan.add(panOffset);

		};

		// pass in distance in world space to move up
		this.panUp = function (distance) {

			var te = this.object.matrix.elements;

			// get Y column of matrix
			panOffset.set(te[4], te[5], te[6]);
			panOffset.multiplyScalar(distance);

			pan.add(panOffset);

		};

		// pass in x,y of change desired in pixel space,
		// right and down are positive
		this.pan = function (deltaX, deltaY) {

			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

			if (scope.object.fov !== undefined) {

				// perspective
				var position = scope.object.position;
				var offset = position.clone().sub(scope.target);
				var targetDistance = offset.length();

				// half of the fov is center to top of screen
				targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0);

				// we actually don't use screenWidth, since perspective camera is fixed to screen height
				scope.panLeft(2 * deltaX * targetDistance / element.clientHeight);
				scope.panUp(2 * deltaY * targetDistance / element.clientHeight);

			} else if (scope.object.top !== undefined) {

				// orthographic
				scope.panLeft(deltaX * (scope.object.right - scope.object.left) / element.clientWidth);
				scope.panUp(deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight);

			} else {

				// camera neither orthographic or perspective
				console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.');

			}

		};

		this.dollyIn = function (dollyScale) {

			if (dollyScale === undefined) {

				dollyScale = getZoomScale();

			}

			scale /= dollyScale;

		};

		this.dollyOut = function (dollyScale) {

			if (dollyScale === undefined) {

				dollyScale = getZoomScale();

			}

			scale *= dollyScale;

		};

		this.update = function () {

			if (scope.object.top !== undefined) {
				this.object.top = (scale * this.object.top);
				this.object.bottom = (scale * this.object.bottom);
				this.object.left = (scale * this.object.left);
				this.object.right = (scale * this.object.right);

				this.object.updateProjectionMatrix();

			}


			var position = this.object.position;

			offset.copy(position).sub(this.target);
			//
			//		// angle from z-axis around y-axis
			//
			//		var theta = Math.atan2( offset.x, offset.z );
			//
			//		// angle from y-axis
			//
			//		var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
			//
			//		if ( this.autoRotate ) {
			//
			//			this.rotateLeft( getAutoRotationAngle() );
			//
			//		}
			//
			//		theta += thetaDelta;
			//		phi += phiDelta;
			//
			//		// restrict phi to be between desired limits
			//		phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
			//
			//		// restrict phi to be betwee EPS and PI-EPS
			//		phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
			//
			//		var radius = offset.length() * scale;
			//
			//		// restrict radius to be between desired limits
			//		radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
			//		
			//		// move target to panned location
			this.target.add(pan);
			//		
			//		offset.x = radius * Math.sin( phi ) * Math.sin( theta );
			//		offset.y = radius * Math.cos( phi );
			//		offset.z = radius * Math.sin( phi ) * Math.cos( theta );
			//
			position.copy(this.target).add(offset);

			this.object.lookAt(this.target);


			//		if ( lastPosition.distanceTo( this.object.position ) > 0 || scale !== 1 ) {
			this.dispatchEvent(changeEvent);

			//			lastPosition.copy( this.object.position );

			//		}

			//        thetaDelta = 0;
			//		phiDelta = 0;
			scale = 1;
			pan.set(0, 0, 0);

		};


		this.reset = function () {

			state = STATE.NONE;

			this.target.copy(this.target0);
			this.object.position.copy(this.position0);

			this.update();

		};

		function getAutoRotationAngle() {

			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;

		}

		function getZoomScale() {

			return Math.pow(0.95, scope.zoomSpeed);

		}

		function onMouseDown(event) {

			if (scope.enabled === false) return;
			event.preventDefault();
			// console.log(event.button)
			if (event.button === 0) {
				//if ( scope.noRotate === true ) return;
				//STATE.ROTATE;
				//rotateStart.set( event.clientX, event.clientY );

				if (scope.noPan === true) return;
				state = STATE.PAN;

				panStart.set(event.clientX, event.clientY);

			} else if (event.button === 1) {
				if (scope.noZoom === true) return;

				state = STATE.DOLLY;

				dollyStart.set(event.clientX, event.clientY);

			} else if (event.button === 2) {
				if (scope.noPan === true) return;

				state = STATE.PAN;

				panStart.set(event.clientX, event.clientY);

			}

			scope.domElement.addEventListener('mousemove', onMouseMove, false);
			scope.domElement.addEventListener('mouseup', onMouseUp, false);
			scope.dispatchEvent(startEvent);

		}

		function onMouseMove(event) {

			if (scope.enabled === false) return;

			event.preventDefault();

			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

			if (state === STATE.ROTATE) {

				if (scope.noRotate === true) return;

				rotateEnd.set(event.clientX, event.clientY);
				rotateDelta.subVectors(rotateEnd, rotateStart);

				// rotating across whole screen goes 360 degrees around
				scope.rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);

				// rotating up and down along whole screen attempts to go 360, but limited to 180
				scope.rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);

				rotateStart.copy(rotateEnd);

			} else if (state === STATE.DOLLY) {

				if (scope.noZoom === true) return;

				dollyEnd.set(event.clientX, event.clientY);
				dollyDelta.subVectors(dollyEnd, dollyStart);

				if (dollyDelta.y > 0) {

					scope.dollyIn();

				} else {

					scope.dollyOut();

				}

				dollyStart.copy(dollyEnd);

			} else if (state === STATE.PAN) {

				if (scope.noPan === true) return;

				panEnd.set(event.clientX, event.clientY);
				panDelta.subVectors(panEnd, panStart);

				scope.pan(panDelta.x, panDelta.y);

				panStart.copy(panEnd);

			}

			scope.update();

		}

		function onMouseUp( /* event */) {

			if (scope.enabled === false) return;
			scope.domElement.removeEventListener('mousemove', onMouseMove, false);
			scope.domElement.removeEventListener('mouseup', onMouseUp, false);
			scope.dispatchEvent(endEvent);
			state = STATE.NONE;

		}

		function onMouseWheel(event) {
			if (scope.enabled === false || scope.noZoom === true) return;
			event.preventDefault();
			var delta = 0;

			if (event.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9

				delta = event.wheelDelta;

			} else if (event.detail !== undefined) { // Firefox

				delta = - event.detail;

			}

			if (delta > 0) {

				scope.dollyOut();

			} else {

				scope.dollyIn();

			}
			scope.update();
			scope.dispatchEvent(startEvent);
			scope.dispatchEvent(endEvent);

		}

		function onKeyDown(event) {

			if (scope.enabled === false || scope.noKeys === true || scope.noPan === true) return;

			switch (event.keyCode) {

				case scope.keys.UP:
					scope.pan(0, scope.keyPanSpeed);
					scope.update();
					break;

				case scope.keys.BOTTOM:
					scope.pan(0, - scope.keyPanSpeed);
					scope.update();
					break;

				case scope.keys.LEFT:
					scope.pan(scope.keyPanSpeed, 0);
					scope.update();
					break;

				case scope.keys.RIGHT:
					scope.pan(- scope.keyPanSpeed, 0);
					scope.update();
					break;

			}

		}

		function touchstart(event) {

			if (scope.enabled === false) return;

			switch (event.touches.length) {

				case 1:	// one-fingered touch: rotate

					if (scope.noRotate === true) return;

					state = STATE.TOUCH_ROTATE;

					rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
					break;

				case 2:	// two-fingered touch: dolly

					if (scope.noZoom === true) return;

					state = STATE.TOUCH_DOLLY;

					var dx = event.touches[0].pageX - event.touches[1].pageX;
					var dy = event.touches[0].pageY - event.touches[1].pageY;
					var distance = Math.sqrt(dx * dx + dy * dy);
					dollyStart.set(0, distance);
					break;

				case 3: // three-fingered touch: pan

					if (scope.noPan === true) return;

					state = STATE.TOUCH_PAN;

					panStart.set(event.touches[0].pageX, event.touches[0].pageY);
					break;

				default:

					state = STATE.NONE;

			}

			scope.dispatchEvent(startEvent);

		}

		function touchmove(event) {

			if (scope.enabled === false) return;

			event.preventDefault();
			event.stopPropagation();

			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

			switch (event.touches.length) {

				case 1: // one-fingered touch: rotate

					if (scope.noRotate === true) return;
					if (state !== STATE.TOUCH_ROTATE) return;

					rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
					rotateDelta.subVectors(rotateEnd, rotateStart);

					// rotating across whole screen goes 360 degrees around
					scope.rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);
					// rotating up and down along whole screen attempts to go 360, but limited to 180
					scope.rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);

					rotateStart.copy(rotateEnd);

					scope.update();
					break;

				case 2: // two-fingered touch: dolly

					if (scope.noZoom === true) return;
					if (state !== STATE.TOUCH_DOLLY) return;

					var dx = event.touches[0].pageX - event.touches[1].pageX;
					var dy = event.touches[0].pageY - event.touches[1].pageY;
					var distance = Math.sqrt(dx * dx + dy * dy);

					dollyEnd.set(0, distance);
					dollyDelta.subVectors(dollyEnd, dollyStart);

					if (dollyDelta.y > 0) {

						scope.dollyOut();

					} else {

						scope.dollyIn();

					}

					dollyStart.copy(dollyEnd);

					scope.update();
					break;

				case 3: // three-fingered touch: pan

					if (scope.noPan === true) return;
					if (state !== STATE.TOUCH_PAN) return;

					panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
					panDelta.subVectors(panEnd, panStart);

					scope.pan(panDelta.x, panDelta.y);

					panStart.copy(panEnd);

					scope.update();
					break;

				default:

					state = STATE.NONE;

			}

		}

		function touchend( /* event */) {

			if (scope.enabled === false) return;

			scope.dispatchEvent(endEvent);
			state = STATE.NONE;

		}

		this.domElement.addEventListener('contextmenu', function (event) { event.preventDefault(); }, false);
		this.domElement.addEventListener('mousedown', onMouseDown, false);
		this.domElement.addEventListener('mousewheel', onMouseWheel, false);
		this.domElement.addEventListener('DOMMouseScroll', onMouseWheel, false); // firefox

		this.domElement.addEventListener('touchstart', touchstart, false);
		this.domElement.addEventListener('touchend', touchend, false);
		this.domElement.addEventListener('touchmove', touchmove, false);

		// window.addEventListener('keydown', onKeyDown, false);//取消对键盘上下左右的监听

	};
	OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
	return OrbitControls;
};